Skip to content

Add admin API token fallback for watcher notifications#1298

Open
nevyangelova wants to merge 2 commits intomasterfrom
MM-68049-admin-token-watcher-fallback
Open

Add admin API token fallback for watcher notifications#1298
nevyangelova wants to merge 2 commits intomasterfrom
MM-68049-admin-token-watcher-fallback

Conversation

@nevyangelova
Copy link
Copy Markdown
Contributor

@nevyangelova nevyangelova commented Apr 13, 2026

Summary

When none of the issue's Creator, Assignee, or Reporter have connected Mattermost accounts, watcher notifications silently fail because the plugin cannot obtain a Jira API client to fetch the watcher list. This adds a fallback to use the configured Admin API Token to retrieve watchers, matching the existing fallback pattern used for issue data and project list fetching.

Ticket Link

https://mattermost.atlassian.net/browse/MM-68049

Change Impact: 🟠 Medium

Reasoning: The PR adds a fallback mechanism to retrieve issue watchers using an admin API token when no connected user client is available, following existing patterns in the codebase. However, the new GetWatchersWithAPIToken method and its integration into the checkIssueWatchers flow lack dedicated test coverage for the token-based fallback paths.

Regression Risk: Low to moderate. The change follows established fallback patterns already used for GetIssueData and GetProjectListData. Error handling is explicit—hard errors from fetchConnectedUser return immediately, while the fallback only triggers when client == nil. The token-based HTTP call uses a 30-second timeout to prevent indefinite blocking. However, the new code paths (token-based watcher retrieval) have not been explicitly tested, creating untested notification flow scenarios.

QA Recommendation: Manual QA should focus on watcher notification scenarios where no connected user exists but an admin token is configured. Test both success and failure cases: (1) valid admin token retrieving watchers successfully, (2) missing/invalid token, (3) unresponsive Jira server (timeout handling), and (4) malformed responses. Automated test coverage for the new GetWatchersWithAPIToken method and its integration paths should be added before merging. Risk of skipping manual QA is moderate due to the gap in test coverage for token-based fallback flows.

@nevyangelova nevyangelova requested a review from a team as a code owner April 13, 2026 13:59
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

checkIssueWatchers now treats connected-user fetch errors as terminal; if no connected Jira client is available and AdminAPIToken is configured, it falls back to fetching issue watchers via a new exported GetWatchersWithAPIToken HTTP call to the Jira REST API.

Changes

Cohort / File(s) Summary
Issue Watchers Fallback
server/issue.go
Updated checkIssueWatchers to: (1) return on wh.fetchConnectedUser(...) errors, (2) use connected client GetWatchers(...) when present, or (3) fallback to new GetWatchersWithAPIToken(issueID, instanceID) when AdminAPIToken is set. Added exported GetWatchersWithAPIToken which issues GET /rest/api/2/issue/{issueID}/watchers, validates 200 OK and non-nil body, and unmarshals into jira.Watches, returning annotated errors on failure.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant checkIssueWatchers
    participant ConnectedUserClient
    participant AdminTokenClient
    participant JiraAPI

    Client->>checkIssueWatchers: trigger watcher check
    checkIssueWatchers->>ConnectedUserClient: fetch connected user client
    alt fetchConnectedUser returns error
        checkIssueWatchers-->>Client: return (terminal error)
    else connected client != nil
        checkIssueWatchers->>ConnectedUserClient: GetWatchers(issueID)
        ConnectedUserClient->>JiraAPI: GET /rest/api/2/issue/{issueID}/watchers (user auth)
        JiraAPI-->>ConnectedUserClient: 200 + watchers JSON
        ConnectedUserClient-->>checkIssueWatchers: watchers
    else connected client is nil and AdminAPIToken set
        checkIssueWatchers->>AdminTokenClient: GetWatchersWithAPIToken(issueID, instanceID)
        AdminTokenClient->>JiraAPI: GET /rest/api/2/issue/{issueID}/watchers (admin token)
        JiraAPI-->>AdminTokenClient: 200 + watchers JSON
        AdminTokenClient->>AdminTokenClient: validate + unmarshal JSON
        AdminTokenClient-->>checkIssueWatchers: jira.Watches
    end
    checkIssueWatchers-->>Client: complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add admin API token fallback for watcher notifications' directly and accurately reflects the main change: adding a fallback mechanism using admin API tokens to retrieve watcher notifications when a connected client is unavailable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-68049-admin-token-watcher-fallback

Comment @coderabbitai help to get the list of available commands and usage tips.

@dryrunsecurity
Copy link
Copy Markdown

dryrunsecurity bot commented Apr 13, 2026

DryRun Security

This pull request introduces a critical potential Server-Side Request Forgery (SSRF) in server/issue.go: GetWatchersWithAPIToken constructs and requests a URL using an unvalidated instanceID parameter, allowing an attacker-controlled value (including internal IPs) to trigger arbitrary HTTP requests. The finding is rated critical but non-blocking and should be fixed by validating/restricting instanceID, enforcing allowed hosts or schemes, and using safe HTTP client patterns.

🔴 Potential Server-Side Request Forgery (SSRF) in server/issue.go (drs_2fe599a7)
Vulnerability Potential Server-Side Request Forgery (SSRF)
Description GetWatchersWithAPIToken constructs a URL using the instanceID parameter as the base URL and performs an HTTP request (httpClient.Do) without validating or restricting the instanceID value. If instanceID is attacker-controllable and contains an arbitrary URL (including internal IPs), this allows SSRF.

req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", instanceID, issueID), nil)
if err != nil {
return nil, errors.Wrapf(err, "failed to create http request for fetching watchers. IssueID: %s", issueID)
}


Comment to provide feedback on these findings.

Report false positive: @dryrunsecurity fp [FINDING ID] [FEEDBACK]
Report low-impact: @dryrunsecurity nit [FINDING ID] [FEEDBACK]

Example: @dryrunsecurity fp drs_90eda195 This code is not user-facing

All finding details can be found in the DryRun Security Dashboard.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/issue.go`:
- Around line 1684-1694: The current branch treats a non-nil err from the
connected-user lookup the same as a nil client (no linked user) and falls back
to the admin token; change the logic so that if err != nil you log and return
immediately (using p.errorf and the existing error message), and only when err
== nil && client == nil (i.e., no linked Jira user) should you attempt the
admin-token fallback via p.GetWatchersWithAPIToken; keep references to
GetWatchersWithAPIToken, p.getConfig().AdminAPIToken, and p.errorf to locate and
update the branch accordingly.
- Around line 1907-1919: The http.Client used in the watcher fetch is created
without a timeout which can hang; update the code in GetIssueWatchers (and
similarly in GetIssueDataWithAPIToken and GetProjectListWithAPIToken) to
construct an http.Client with a sensible Timeout (e.g., time.Second * X) before
calling http.NewRequest/Do, and reuse that client variable (httpClient) for the
request; ensure you import time if not present and preserve existing error
handling (SetAdminAPITokenRequestHeader, errors.Wrapf) while setting the timeout
on the client prior to httpClient.Do.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 5b04062e-10db-4a05-b6d1-71d7370808b8

📥 Commits

Reviewing files that changed from the base of the PR and between 737f3cb and 90a4ca8.

📒 Files selected for processing (1)
  • server/issue.go

Comment thread server/issue.go Outdated
Comment thread server/issue.go Outdated
…ent timeout

- Only fall back to admin API token when no connected user is found (client == nil),
  not on hard errors like instance store failures
- Add 30s timeout to HTTP client in GetWatchersWithAPIToken to prevent indefinite
  blocking on unresponsive Jira servers

Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/issue.go`:
- Around line 1911-1915: The request URL in GetWatchersWithAPIToken currently
uses the store key parameter instanceID instead of the Jira base URL; update the
function to resolve the instance object (via the existing LoadInstance or
equivalent on Plugin) using the instanceID key and use the instance's
BaseURL/URL (e.g., instance.BaseURL or instance.URL) when formatting the request
to "/rest/api/2/issue/{issueID}/watchers"; ensure you still handle LoadInstance
errors and preserve the existing error wrapping and http client usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: f7e43f74-f92b-43b9-ac05-d6cc380303bc

📥 Commits

Reviewing files that changed from the base of the PR and between 90a4ca8 and a0ade1c.

📒 Files selected for processing (1)
  • server/issue.go

Comment thread server/issue.go
@nang2049
Copy link
Copy Markdown

@coderabbitai review

@nang2049 nang2049 requested a review from ogi-m April 15, 2026 13:43
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@avasconcelos114 avasconcelos114 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes LGTM! It might also be good to cover this new fallback scenario with test cases in issue_test.go (I'd consider it non-blocking though)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants